package com.cs252.AudioTest;
import java.net.*;
import java.util.ArrayList;
import java.io.*;
public class Server 
{
	ArrayList<ClientEntry> clients = new ArrayList<ClientEntry>();//Holds connected clients
	final int PORT = 4400;
	final int UPORT = 4401;
	//This class is responsible for administering TDP and UDP transfer
    public Server()
    {
    	udpOpen();//Start the UDP bouncer
    	firePrinter();//Start the printer timer
    	ServerSocket sSock = null;
		try
		{
			sSock = new ServerSocket(PORT);//Listen on TCP
		}
		catch(Exception e)
		{
			System.out.println("ServerSocket: " + e);
		}
		
		//Accept clients continuously
    	while(true)
    	{
			try
			{
				Socket socket;
				socket = sSock.accept();
			    handleClient(socket);
		    }
		    catch(Exception e)
		    {
		    	System.out.println("Accept & start: " + e);
		    }
    	}
    }
    //Sends out the names to all clients
    synchronized public void broadcastNames()
    {
    	//foreach client
    	for(ClientEntry c: clients)
    	{
    		if(c.printer == null || c == null)
    			continue;
    		//send all the client names
    		c.printer.println("nameup");
    		for(ClientEntry d: clients)
    		{
    			if(c != d && d !=null)
    				c.printer.println((d.name + ((d.buddy != null) ? ("*"):(""))));
    		}
    		c.printer.println("end");
    	}
    }
    //Listenter for a client
    public void handleClient(final Socket socket)
    {
    	Runnable fireCommand = new Runnable()
		{	
			public void run()
			{
				ClientEntry ce = new ClientEntry();
				clients.add(ce);
				try
				{
					//INITIALIZE
					ce.sock = socket;
					// Read data from the client
					InputStream stream = socket.getInputStream();
					// InputStream is an abstract class. We needed to use a subclass
					BufferedReader data = new BufferedReader(new InputStreamReader(stream));
					ce.name = data.readLine();
					System.out.println("Connection made, Username: " + ce.name);
					PrintWriter out = new PrintWriter(new BufferedWriter(
						new OutputStreamWriter(socket.getOutputStream())), true);
					ce.printer = out;
					broadcastNames();
					//LISTEN
					while(true)
					{
						String str = data.readLine();//read msg
						//if client wants to connect to someone
						if(str.contains("c2: "))
						{
							//get the name of the person they want to connect to
							//remove a star if it is the last character
							//this star usually means a person is in a call, 
							//the * is appended to those names
							String connectee = str.substring(4, str.length());
							if(connectee.charAt(connectee.length() - 1) == '*')
								connectee = connectee.substring(0, connectee.length() -1);
							for(ClientEntry c:clients)
							{
								//if we find the client
								if(c.name.equals(connectee)){
									//bad request if they are in a call
									if(c.buddy != null){
										ce.printer.println("n");
										break;
									}
									//if not bad request, go ahead with call
									conversate(c, ce);
									broadcastNames();
									break;
								}
							}
						}
						//call accepted, make it work by setting bridge to true
						else if(str.contains("yes"))
						{
							ce.buddy.bridge = true;
							ce.bridge = true;
						}
						//end call
						else if(str.contains("end") && ce.buddy != null){
							//tell the other person to edn
							ce.buddy.printer.println("end");
							ce.buddy.buddy = null;
							ce.buddy = null;
							//rebroadcast
							broadcastNames();
						}
						else
							System.out.println("malformed command");
					}
				}
				catch(Exception e)
				{
					System.out.println("User has left: " + ce.name);
					clients.remove(ce);
					broadcastNames();
				}
			}
		};
		new Thread(fireCommand).start();
    }
    //make a call happen
    public void conversate(ClientEntry a, ClientEntry b)
    {
    	if(a==null || b==null)
    		return;
    	//output
    	System.out.println("Call request between \"" + a.name + "\" and \"" + b.name + "\"");
    	//Request to say that you are being called and theyre name
    	a.printer.println("1UDPIN" + clients.indexOf(b));
    	a.printer.println(b.name);
    	a.printer.flush();
    	//request to say that you called, and to start recording
    	b.printer.println("0UDPIN" + clients.indexOf(a));
    	b.printer.println(a.name);
    	b.printer.flush();
    	//call has not been accepted yet, bridge is false
    	a.bridge = b.bridge = false;
    	//buddy is index of person connected
    	a.buddy = b;
    	b.buddy = a;
    }
    
	public void udpOpen()
	{
		Runnable fireCommand = new Runnable()
		{
			public void run()
			{
				try
				{
					//Open dg socket
					DatagramSocket socket = new DatagramSocket(UPORT);
					while(true)
					{
						//accept packet
						byte[] buf = new byte[4101];
						/* Prepare a UDP-Packet that can
						 * contain the data we want to receive */
						DatagramPacket packet = new DatagramPacket(buf, buf.length);
						socket.receive(packet);
						//if packet doesnt begin with 55, it isn't ours
						if(buf[0] != 55)
							continue;
						//if the client destination is bad, drop packet
						if(buf[1] >= clients.size() || buf[1] < 0)
							continue;
						//pullout the destination client
						ClientEntry friend = clients.get(buf[1]);
						//set the address to him
						packet.setAddress(friend.sock.getInetAddress());
						packet.setPort(4402);
						//send it so long as the call has been accepted
						if(friend.bridge)
							socket.send(packet);
					}
				}
				catch(Exception e)
				{
					System.out.println("UDPERR" + e);
				}
			}
		};
		new Thread(fireCommand).start();
	}
	//Periodically prints the username to the terminal
	public void firePrinter()
	{
		Runnable fireCommand = new Runnable()
		{
			public void run()
			{
				while(true){
					try
					{
						Thread.sleep(60000);
					}
					catch(Exception e)
					{
						System.out.println("errr");
					}
					System.out.println("\nUser Listing Update:: ");
		    		for(ClientEntry d: clients)
		    		{
		    			if(d !=null)
		    				System.out.println("\t" + (d.name + ((d.buddy != null) ? ("*"):(""))));
		    		}
				}
			}
		};
		new Thread(fireCommand).start();
	}
    public static void main(String[] args)
    {
    	new Server();
    }
}